/**
 * @Author: colpu
 * @Date: 2025-03-29 19:38:16
 * @LastEditors: colpu ycg520520@qq.com
 * @LastEditTime: 2025-10-21 17:31:01
 * @
 * @Copyright (c) 2025 by colpu, All Rights Reserved.
 */
import is from "is-type-of";
import path from "node:path";
import { globbySync } from "globby";
import { readFileSync, statSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { stat } from "node:fs/promises";
import BuiltinModule from "node:module";
import { exec } from "child_process";
import debugFactory from "debug";
const debug = debugFactory("core:utils");
import { FULLPATH } from "../constants.js";

// Guard against poorly mocked module constructors.
const Module =
  typeof module !== "undefined" && module.constructor.length > 1
    ? module.constructor
    : /* istanbul ignore next */
    BuiltinModule;

export const extensions = Module._extensions;
export const extensionNames = Object.keys(extensions).concat([
  ".cjs",
  ".mjs",
  ".ts",
]);
debug("Module extensions: %j", extensionNames);

export function deprecated(message) {
  if (debug.enabled) {
    console.trace("[@colpu/core/deprecated] %s", message);
  } else {
    console.log("[@colpu/core/deprecated] %s", message);
    console.log(
      "[@colpu/core/deprecated] set NODE_DEBUG=@colpu/core/utils can show call stack"
    );
  }
}

export async function existsPath(filepath) {
  try {
    await stat(filepath);
    return true;
  } catch {
    return false;
  }
}
/**
 * @function defaultCamelize 默认驼峰话文件
 * @param {String} fileString
 * @param {String} caseStyle 驼峰风格lower(小写),upper（大写）,camel（驼峰）
 * use default camelize, will capitalize the first letter
 * foo_bar.js > FooBar
 * fooBar.js  > FooBar
 * FooBar.js  > FooBar
 * FooBar.js  > FooBar
 * FooBar.js  > fooBar (if lowercaseFirst is true)
 */
export function defaultCamelize(fileName, caseStyle) {
  if (!/^[a-z][a-z0-9_-]*$/i.test(fileName)) {
    throw new Error(`${fileName} is not match 'a-z0-9_-'`);
  }
  fileName = fileName.replace(/[_-][a-z]/gi, (s) =>
    s.substring(1).toUpperCase()
  );
  let first = fileName[0];
  switch (caseStyle) {
    case "lower":
      first = first.toLowerCase();
      break;
    case "upper":
      first = first.toUpperCase();
      break;
    case "camel":
    default:
  }
  return first + fileName.substring(1);
}
/**
 * @function callFn 调用方法
 * @param {Function} fn 需要调用的方法
 * @param {Array} args 参数数组
 * @param {Object} ctx 调用方法的上下文
 */
export function callFn(fn, args, ctx) {
  args = args || [];
  if (!is.function(fn)) {
    return fn;
  }
  return ctx ? fn.call(ctx, ...args) : fn(...args);
}

function fixFilepath(filepath) {
  if (filepath.startsWith("file://")) {
    filepath = fileURLToPath(filepath);
  }
  return filepath;
}

/*******
 * @function loadFile
 * @description: 动态加载文件
 * @param {String} filepath 文件路径必须是绝对路径或者以file:///开头的文件
 * @param {Boolean} importDefaultOnly
 * @return {Promise}
 */
export async function loadFile(filepath, importDefaultOnly = true) {
  filepath = fixFilepath(filepath);
  try {
    // 如果不是js模块，只返回内容缓冲区
    const extname = path.extname(filepath);
    if (extname && !extensionNames.includes(extname)) {
      return readFileSync(filepath);
    }
    const promise = import(filepath).then((mod) => {
      if (importDefaultOnly) return mod.default;
      return mod;
    });
    return promise;
  } catch (e) {
    if (!(e instanceof Error)) {
      console.trace(e);
      throw e;
    }
    const err = new Error(
      `[@colpu/core/utils] load file: ${filepath}, error: ${e.message}`
    );
    err.cause = e;
    debug("[loadFile] handle %s error: %s", filepath, e);
    throw err;
  }
}

export function getResolvedFilename(filepath, baseDir) {
  const reg = /[/\\]/g;
  return filepath.replace(baseDir + path.sep, "").replace(reg, "/");
}

export function readJSONSync(filepath) {
  return JSON.parse(readFileSync(fixFilepath(filepath), "utf8"));
}

export function getLocalIP(callback) {
  exec("ifconfig", (err, stdout, stderr) => {
    if (err) {
      callback(err, null);
      return;
    }
    const pattern = /inet (\d+\.\d+\.\d+\.\d+)/g;
    const matches = stdout.match(pattern);
    if (matches && matches.length > 0) {
      callback(
        undefined,
        matches.map((ip) => ip.replace("inet ", ""))
      );
    } else {
      callback("No IP Found", null);
    }
  });
}


/**
 * @function wrapClass 包裹类方便后面用中间件
 * @param {Function} Controller 控制器类
 */
export function wrapClass(Controller) {
  let proto = Controller.prototype;
  const ret = {};
  // 遍历原型链
  while (proto !== Object.prototype) {
    const keys = Object.getOwnPropertyNames(proto);
    for (const key of keys) {
      // getOwnPropertyNames 将返回构造函数
      // 我们将忽略他
      if (key === "constructor") {
        continue;
      }

      // 跳过getter, setter & non-function 属性properties
      const d = Object.getOwnPropertyDescriptor(proto, key);

      // 防止重写子方法prevent to override sub method
      if (typeof d.value === "function" && !ret.hasOwnProperty(key)) {
        ret[key] = methodToMiddleware(Controller, key);
        ret[key][FULLPATH] = `${proto.fullpath}#${proto.pathname}.${key}()`;
      }
    }
    proto = Object.getPrototypeOf(proto);
  }
  return ret;

  function methodToMiddleware(Controller, key) {
    return function classControllerMiddleware(...args) {
      const controller = new Controller(...args);
      return callFn(controller[key], args, controller);
    };
  }
}

export function isDirectory(dir) {
  let stat;
  try {
    stat = statSync(dir);
  } catch (error) {
    debug("[isDir] statSync error: %s", error);
  }
  return stat && stat.isDirectory();
}

export function addDirectory(dir, directory = '') {
  if (isDirectory(dir)) {
    const filepaths = globbySync('*', {
      onlyDirectories: true,
      deep: 1,
      cwd: dir,
    });
    return filepaths.map(filepath => path.join(dir, filepath, directory));
  }
  return [];
}
